﻿using System;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace ZoeEngine.Services.Input
{
    public class InputProvider : GameComponent, IInputService
    {
        private KeyboardState _lastKeyboardState;
        private KeyboardState _currKeyboardState;

        private MouseState _lastMouseState;
        private MouseState _currMouseState;
        private MouseButton _lastMouseButtons;
        private MouseButton _currMouseButtons;

        private GamePadState _lastGamePadState;
        private GamePadState _currGamePadState;

        /// <summary>
        /// Default constructor. Registers this class as a game service.
        /// </summary>
        /// <param name="game">Game instance requesting service.</param>
        public InputProvider(Game game)
            : base(game)
        {
            if (game.Services.GetService(typeof(IInputService)) != null)
            {
                throw new ArgumentException("An input provider already exists.");
            }
            game.Services.AddService(typeof(IInputService), this);
        }


        /// <summary>
        /// Fired when a keyboard key is pressed.
        /// </summary>
        public event EventHandler<KeyboardEventArgs> KeyboardKeyDown;
        /// <summary>
        /// Fired every consecutive update that a key is held down.
        /// </summary>
        public event EventHandler<KeyboardEventArgs> KeyboardKeyHeld;
        /// <summary>
        /// Fired when a keyboard key is released.
        /// </summary>
        public event EventHandler<KeyboardEventArgs> KeyboardKeyUp;

        /// <summary>
        /// Fired when a mouse button is pressed.
        /// </summary>
        public event EventHandler<MouseEventArgs> MouseButtonPress;
        /// <summary>
        /// Fired every consecutive update that a mouse button is held down.
        /// </summary>
        public event EventHandler<MouseEventArgs> MouseButtonHeld;
        /// <summary>
        /// Fired when a mouse button is released.
        /// </summary>
        public event EventHandler<MouseEventArgs> MouseButtonRelease;
        /// <summary>
        /// Fired when the mouse is moved and the cursor changes position.
        /// </summary>
        public event EventHandler<MouseEventArgs> MouseMotion;

        /// <summary>
        /// Resets all event delegates.
        /// </summary>
        public void ClearListeners()
        {
            KeyboardKeyUp = null;
            KeyboardKeyHeld = null;
            KeyboardKeyDown = null;

            MouseButtonPress = null;
            MouseButtonHeld = null;
            MouseButtonRelease = null;
            MouseMotion = null;
        }

        protected void OnKeyboardKeyDown(KeyboardEventArgs e)
        {
            if (KeyboardKeyDown != null)
            {
                KeyboardKeyDown(this, e);
            }
        }
        protected void OnKeyboardKeyHeld(KeyboardEventArgs e)
        {
            if (KeyboardKeyHeld != null)
            {
                KeyboardKeyHeld(this, e);
            }
        }
        protected void OnKeyboardKeyUp(KeyboardEventArgs e)
        {
            if (KeyboardKeyUp != null)
            {
                KeyboardKeyUp(this, e);
            }
        }

        protected void OnMouseButtonPress(MouseEventArgs e)
        {
            if (MouseButtonPress != null)
            {
                MouseButtonPress(this, e);
            }
        }
        protected void OnMouseButtonHeld(MouseEventArgs e)
        {
            if (MouseButtonHeld != null)
            {
                MouseButtonHeld(this, e);
            }
        }
        protected void OnMouseButtonRelease(MouseEventArgs e)
        {
            if (MouseButtonRelease != null)
            {
                MouseButtonRelease(this, e);
            }
        }
        protected void OnMouseMotion(MouseEventArgs e)
        {
            if (MouseMotion != null)
            {
                MouseMotion(this, e);
            }
        }

        /// <summary>
        /// Initializes the input device states.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            _lastKeyboardState = Keyboard.GetState();
            
            _lastMouseState = Mouse.GetState();
            _lastMouseButtons |= _lastMouseState.LeftButton == ButtonState.Pressed ? MouseButton.Left : MouseButton.None;
            _lastMouseButtons |= _lastMouseState.MiddleButton == ButtonState.Pressed ? MouseButton.Middle : MouseButton.None;
            _lastMouseButtons |= _lastMouseState.RightButton == ButtonState.Pressed ? MouseButton.Right : MouseButton.None;
        }
        /// <summary>
        /// Updates the input device states and determines if an action has occurred.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void Update(GameTime gameTime)
        {
            // Update Keyboard State
            _currKeyboardState = Keyboard.GetState();

            Keys[] lastDown = _lastKeyboardState.GetPressedKeys();
            for (int i = 0; i < lastDown.Length; i++)
            {
                if (_currKeyboardState.IsKeyUp(lastDown[i]))
                    OnKeyboardKeyUp(new KeyboardEventArgs(lastDown[i], Keys.None, gameTime));
            }

            Keys[] downKeys = _currKeyboardState.GetPressedKeys();
            for (int i = 0; i < downKeys.Length; i++)
            {
                if (_lastKeyboardState.IsKeyDown(downKeys[i]))
                    OnKeyboardKeyHeld(new KeyboardEventArgs(downKeys[i], Keys.None, gameTime)); 
                else
                    OnKeyboardKeyDown(new KeyboardEventArgs(downKeys[i], Keys.None, gameTime));
            }

            _lastKeyboardState = _currKeyboardState;

            // Update Mouse Button States
            _currMouseState = Mouse.GetState();

            _currMouseButtons |= _currMouseState.LeftButton == ButtonState.Pressed ? MouseButton.Left : MouseButton.None;
            _currMouseButtons |= _currMouseState.MiddleButton == ButtonState.Pressed ? MouseButton.Middle : MouseButton.None;
            _currMouseButtons |= _currMouseState.RightButton == ButtonState.Pressed ? MouseButton.Right : MouseButton.None;

            MouseButton pressedButtons = (_lastMouseButtons ^ _currMouseButtons) & _currMouseButtons;
            MouseButton releasedButtons = (_lastMouseButtons ^ _currMouseButtons) & _lastMouseButtons;
            MouseButton heldButtons = _lastMouseButtons & _currMouseButtons;

            if (releasedButtons != 0)
            {
                OnMouseButtonRelease(new MouseEventArgs(_currMouseState.X, _currMouseState.Y, releasedButtons, gameTime));
            }

            if (pressedButtons != 0)
            {
                OnMouseButtonPress(new MouseEventArgs(_currMouseState.X, _currMouseState.Y, pressedButtons, gameTime));
            }

            if (heldButtons != 0)
            {
                OnMouseButtonHeld(new MouseEventArgs(_currMouseState.X, _currMouseState.Y, heldButtons, gameTime));
            }

            if (_currMouseState.X != _lastMouseState.X || _currMouseState.Y != _lastMouseState.Y)
            {
                OnMouseMotion(new MouseEventArgs(_currMouseState.X, _currMouseState.Y, _currMouseButtons, gameTime));
            }

            _lastMouseState = _currMouseState;
            _lastMouseButtons = _currMouseButtons;

            // Update Game Pad States
        }
        /// <summary>
        /// Called when service is being disposed.
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.Game.Services.GetService(typeof(IInputService)) == this)
                {
                    this.Game.Services.RemoveService(typeof(IInputService));
                }
            }
            base.Dispose(disposing);
        }
    }
}